ATM取款聊聊『门面模式』

目录:设计模式之小试牛刀
源码路径:Github-Design Pattern


定义:(Facade Pattern)

(也称为外观模式)要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。

类图:

外观模式通用类图

启示:

周末出去吃饭,饭罢付账,打开钱包,就干瘪瘪的几张十元大钞。这可不行,得去取几张毛爷爷啊,下周可不能不吃不喝Debug不是?
找到附近的ATM自动取款机,打开钱包,我擦,我工资卡哪去了?翻了半天,就找到身份证和社保卡,唉,我这猿脑子,忘带银行卡了。
正准备转身离去下次再取,旁边一银行保安说:忘带银行卡啦?试试无卡取款,我银行最近推出了这项新功能,不用插银行卡也能取款,你试试看。
真是山穷水尽疑无路,牧童遥指杏花村。
回到ATM机前,果然屏幕上有个无卡取款功能键。选择无卡取款,然后让输入银行预留手机号,银行发送取款验证码到手机上,回填验证码到ATM,选择银行卡号,就可以取款了,果然方便。

取完款,回家路上,就在暗自思量,这ATM机一般就提供查询余额、取款、存款、转账四大功能。但其实银行的业务可不止这四个,仅仅是银行只针对ATM机开放了这四个功能罢了。

灵光一闪,这不就是门面模式嘛!

取款者可以通过ATM与银行系统进行交互,也可以通过手机银行客户端与银行进行交互,但交互的业务却大不相同,比如手机就不能直接取现金。那咱们就用门面模式来简单实现看看。

代码:

依据面向对象的思想,我们对ATM取款这个简单场景进行抽象。
首先对银行进行简单抽象:
主要定义了查询余额、取款、存款、转账、手机充值功能。

/// <summary>
///     银行现金业务子系统
/// </summary>
public interface IBankSubsystem
{
    /// <summary>
    ///     查询余额
    /// </summary>
    /// <param name="account">银行账户</param>
    /// <returns></returns>
    int CheckBalance(BankAccount account);

    /// <summary>
    ///     取款
    /// </summary>
    /// <param name="account">银行账户</param>
    /// <param name="money">取多少钱</param>
    /// <returns></returns>
    bool WithdrewMoney(BankAccount account, int money);

    /// <summary>
    ///     存款
    /// </summary>
    /// <param name="account">银行账户</param>
    /// <param name="money">存多少钱</param>
    /// <returns></returns>
    bool DepositMoney(BankAccount account, int money);

    /// <summary>
    ///     转账
    /// </summary>
    /// <param name="account">转出账户</param>
    /// <param name="targetNo">目标账户</param>
    /// <param name="money">转多少钱</param>
    /// <returns></returns>
    bool TransferMoney(BankAccount account, string targetNo, int money);

    /// <summary>
    ///     充值话费
    /// </summary>
    /// <param name="phoneNumber">手机号</param>
    /// <param name="account">银行账户</param>
    /// <param name="money">充值多少</param>
    /// <returns></returns>
    bool RechargeMobilePhone(BankAccount account, string phoneNumber, int money);
}

其中依赖了BankAccountBankAccount主要定义了银行账户。

public class BankAccount
{
    public BankAccount(string bankNo, string password, string name, string phone, int totalMoney)
    {
        BankNo = bankNo;
        Password = password;
        Name = name;
        Phone = phone;
        TotalMoney = totalMoney;
    }

    /// <summary>
    ///     银行卡号
    /// </summary>
    public string BankNo { get; set; }

    /// <summary>
    ///     取款密码
    /// </summary>
    public string Password { get; set; }

    /// <summary>
    ///     持卡人姓名
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    ///     手机
    /// </summary>
    public string Phone { get; set; }

    /// <summary>
    ///     总金额
    /// </summary>
    public int TotalMoney { get; set; }
}

紧接着咱们来实现IBankSubsystem

 public class BankSubsystem : IBankSubsystem
{
    /// <summary>
    ///     查询余额
    /// </summary>
    /// <param name="account">银行账户</param>
    /// <returns></returns>
    public int CheckBalance(BankAccount account)
    {
        return account.TotalMoney;
    }

    /// <summary>
    ///     取款
    /// </summary>
    /// <param name="account">银行账户</param>
    /// <param name="money">取多少钱</param>
    /// <returns>余额</returns>
    public bool WithdrewMoney(BankAccount account, int money)
    {
        if (account.TotalMoney >= money)
            account.TotalMoney -= money;
        else
            throw new Exception("余额不足!");

        return true;
    }

    /// <summary>
    ///     转账
    /// </summary>
    /// <param name="account">转出账户</param>
    /// <param name="targetNo">目标账户</param>
    /// <param name="money">转多少钱</param>
    /// <returns></returns>
    public bool TransferMoney(BankAccount account, string targetNo, int money)
    {
        var targetAccount = AccountSubsystem.GetAccount(targetNo);

        if (targetAccount == null)
            throw new Exception("目标账户不存在!");

        if (account.TotalMoney < money)
            throw new Exception("余额不足!");

        account.TotalMoney -= money;
        targetAccount.TotalMoney += money;

        return true;
    }

    /// <summary>
    ///     存款
    /// </summary>
    /// <param name="account">银行账户</param>
    /// <param name="money">存多少钱</param>
    /// <returns></returns>
    public bool DepositMoney(BankAccount account, int money)
    {
        account.TotalMoney += money;
        return true;
    }

    /// <summary>
    ///     充值话费
    /// </summary>
    /// <param name="phoneNumber">手机号</param>
    /// <param name="account">银行账户</param>
    /// <param name="money">充值多少</param>
    /// <returns></returns>
    public bool RechargeMobilePhone(BankAccount account, string phoneNumber, int money)
    {
        throw new NotImplementedException();
    }
}

其中『银行现金子系统』负责金额的查询流转。我们还需要引入『账户管理子系统』来管理银行账户:

/// <summary>
/// 账户管理子系统
/// </summary>
public static class AccountSubsystem
{
    private static readonly List<BankAccount> Accounts = new List<BankAccount>
    {
        new BankAccount("123455", "555555", "圣杰", "138****9309", 1000000),
        new BankAccount("123454", "444444", "产品汪", "157****9309", 2000000),
        new BankAccount("123453", "333333", "运营喵", "154****9309", 3000000),
        new BankAccount("123452", "222222", "程序猿", "187****9309", 4000000),
        new BankAccount("123451", "111111", "设计狮", "189****9309", 5000000)
    };

    public static BankAccount Login(string bankNo, string password)
    {
        var bankAccount = Accounts.FirstOrDefault(a => a.BankNo == bankNo);
        if (bankAccount == null)
            throw new Exception("无效卡号!!!");

        if (bankAccount.Password != password)
            throw new Exception("密码错误!!!");

        return bankAccount;
    }

    public static BankAccount GetAccount(string bankNo)
    {
        var bankAccount = Accounts.FirstOrDefault(a => a.BankNo == bankNo);
        if (bankAccount == null)
            throw new Exception("无效卡号!!!");


        return bankAccount;
    }

    public static void Display(BankAccount account)
    {
        Console.WriteLine("卡号:{0},持卡人姓名:{1},手机号:{2},余额:{3}", account.BankNo, account.Name, account.Phone,
            account.TotalMoney);
    }


    public static bool ChangePassword()
    {
        throw new NotImplementedException();
    }
}

银行账户子系统中预置了一些账户供测试使用。

下面就引入我们这节要讲的门面AtmFacade

 /// <summary>
 ///     ATM机专属门面
 /// </summary>
 public class AtmFacade
 {
     private readonly IBankSubsystem _bankSubsystem = new BankSubsystem();
     private BankAccount _account;

     public void Login(string no, string pwd)
     {
         _account = AccountSubsystem.Login(no, pwd);
     }

     public bool IsLogin()
     {
         return _account != null;
     }

     /// <summary>
     ///     取款
     /// </summary>
     /// <param name="money"></param>
     public void WithdrewCash(int money)
     {
         if (_bankSubsystem.WithdrewMoney(_account, money))
         {
             Console.WriteLine("取款成功!");
             AccountSubsystem.Display(_account);
         }
     }

     /// <summary>
     ///     存款
     /// </summary>
     /// <param name="money"></param>
     public void DepositCash(int money)
     {
         if (_bankSubsystem.DepositMoney(_account, money))
         {
             Console.WriteLine("存款成功!");
             AccountSubsystem.Display(_account);
         }
     }

     /// <summary>
     ///     查余额
     /// </summary>
     public void QueryBalance()
     {
         if (_bankSubsystem.CheckBalance(_account) > 0)
             AccountSubsystem.Display(_account);
     }

     /// <summary>
     ///     转账
     /// </summary>
     /// <param name="targetNo"></param>
     /// <param name="money"></param>
     public void TransferMoney(string targetNo, int money)
     {
         if (_bankSubsystem.TransferMoney(_account, targetNo, money))
         {
             Console.WriteLine("转账成功!");
             AccountSubsystem.Display(_account);
         }
     }
 }

该门面就依赖了IBankSubsystemAccountSubsystem,来负责处理ATM与银行的现金业务系统、银行的账户系统进行交互。

最后上我们的ATM:

public class ATM
{
    public void DisplayUi()
    {
        var facade = new AtmFacade();

        while (true)
            try
            {
                if (!facade.IsLogin())
                {
                    Console.WriteLine("请输入银行卡号:");
                    var bkNo = Console.ReadLine();
                    Console.WriteLine("请输入密码:");
                    var pwd = Console.ReadLine();
                    facade.Login(bkNo, pwd);
                }
                else
                {
                    ShowBusiness(facade);
                }
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(ex.Message);
                Console.ResetColor();
            }
    }


    private static void ShowBusiness(AtmFacade facade)
    {
        Console.WriteLine("==========================================");
        Console.WriteLine("欢迎你!请选择服务项目:");
        Console.WriteLine("1、取款");
        Console.WriteLine("2、存款");
        Console.WriteLine("3、转账");
        Console.WriteLine("4、查询余额");
        Console.WriteLine("5、清屏");
        Console.WriteLine("==========================================");

        var pressKey = Console.ReadKey();

        switch (pressKey.Key)
        {
            case ConsoleKey.D1:
                Console.WriteLine();
                Console.WriteLine("请输入取款金额:");
                var money = Convert.ToInt32(Console.ReadLine());
                facade.WithdrewCash(money);
                break;
            case ConsoleKey.D2:
                Console.WriteLine();
                Console.WriteLine("请输入存款金额:");
                var depositNum = Convert.ToInt32(Console.ReadLine());
                facade.DepositCash(depositNum);
                break;
            case ConsoleKey.D3:
                Console.WriteLine();
                Console.WriteLine("请输入目标账号:");
                var targetNo = Console.ReadLine();
                Console.WriteLine("请输入转账金额:");
                var transferNum = Convert.ToInt32(Console.ReadLine());
                facade.TransferMoney(targetNo, transferNum);
                break;
            case ConsoleKey.D4:
                Console.WriteLine();
                facade.QueryBalance();
                break;
            case ConsoleKey.D5:
                Console.Clear();
                break;
            default:
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("输入有误,请重新输入");
                Console.ResetColor();
                break;
        }
    }

代码太多,可能看的不太一目了然,咱们直接看类型依赖图:


类型依赖图
测试结果

总结:

从类型依赖图中,我们可以清晰的看出AtmFacade是ATM与银行两大子系统进行交互的唯一桥梁,ATM不依赖具体的子系统。且ATM仅能访问门面上提供的功能(查询、转账、取款、存款业务),子系统不用担心暴露过多的接口造成安全问题。
但同时引入一个新的问题,假如ATM需要加入『充值话费、无卡取款、二维码取款』新功能,则需要对门面进行更改,不符合OCP。

优缺点:

优点

  • 减少了系统间的相互依赖,提高了灵活性
  • 提高了安全性(子系统仅能访问门面上开通的方法)

缺点

  • 不符合OCP(门面对象是外界与子系统交互的唯一桥梁)

应用场景:

  • 为一个复杂的模块或子系统提供一个供外界访问的接口
  • 子系统相对独立——外界对子系统的访问只要黑箱操作即可
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容